package com.anb.net;

import com.anb.common.Log;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Runner extends NetObject implements IRunner {

    public static final String DEFAULT_RUNNER = "runner";

    public Runner(String name) throws IOException {
        super(name);
        selector = SelectorProvider.provider().openSelector();
    }

    public Runner() throws IOException {
        super(DEFAULT_RUNNER);
        selector = SelectorProvider.provider().openSelector();
    }

    private Selector selector;

    private List<String> selectorLock = new ArrayList<String>();

    public void run() {
        Log.Get().info("runner starts");
        while (true) {
            if (isStop()) {
                break;
            }
            try {
                synchronized (selectorLock) { }
                //selector.select(100);
                selector.select();
                Iterator iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = (SelectionKey) iterator.next();
                    iterator.remove();

                    if (!key.isValid()) {
                        Log.Get().info("selected invalid");
                        continue;
                    }

                    if (key.isReadable()) {
                        read(key);
                    } else if (key.isAcceptable()) {
                        accept(key);
                    } else if (key.isConnectable()) {
                        connect(key);
                    }
                }
            } catch (Exception e) {
                Log.Get().error("runner error", e);
            }
        }
    }

    @Override
    public void stop() {
        synchronized (selectorLock) {
            selector.wakeup();
            super.stop();
        }
    }

    public void register(Server server) throws IOException {
        Log.Get().info("server " + server.getAlias() + " register");
        synchronized (selectorLock) {
            selector.wakeup();
            server.getChannel().register(selector, SelectionKey.OP_ACCEPT, server);
        }
        Log.Get().info("registered");
    }

    public void register(Client client) throws IOException {
        Log.Get().info("client " + client.getAlias() + " register");
        synchronized (selectorLock) {
            selector.wakeup();
            client.getChannel().register(selector, SelectionKey.OP_CONNECT, client);
        }
        Log.Get().info("registered");
    }

    public void register(ConnectionBase connection) throws IOException {
        Log.Get().info("connection " + connection.getAlias() + " register");
        synchronized (selectorLock) {
            selector.wakeup();
            connection.getChannel().register(selector, SelectionKey.OP_READ, connection);
        }
        Log.Get().info("registered");
    }

    @Override
    public void unregister(Server server) {
        Log.Get().info("server " + server.getAlias() + " unregister");
        SelectionKey key = server.getChannel().keyFor(selector);
        if (key != null) {
            key.cancel();
        }
        Log.Get().info("unregistered");
    }

    @Override
    public void unregister(Client client) {
        Log.Get().info("client " + client.getAlias() + " unregister");
        SelectionKey key = client.getChannel().keyFor(selector);
        if (key != null) {
            key.cancel();
        }
        Log.Get().info("unregistered");
    }

    @Override
    public void unregister(ConnectionBase connection) {
        Log.Get().info("connection " + connection.getAlias() + " unregister");
        SelectionKey key = connection.getChannel().keyFor(selector);
        if (key != null) {
            key.cancel();
        }
        Log.Get().info("unregistered");
    }

    private void accept(SelectionKey key) {
        Server server = (Server)key.attachment();
        server.accepted(key);
    }

    private void connect(SelectionKey key) {
        Client client = (Client)key.attachment();
        client.connected(key);
    }

    private void read(SelectionKey key) {
        ConnectionBase connection = (ConnectionBase)key.attachment();
        connection.read(key);
    }
}
